// ============================================================================
//
// LCP-net framework by Pierre CHATEL chatelp@gmail.com
// Distributed under GPLv3
// Copyright (C) THALES Land & Joint Systems, 2008, 2009. All rights reserved
//
// ============================================================================

/**
 * <copyright>
 * </copyright>
 *
 * $Id$
 */
package com.thalesgroup.preferenceFramework.models.LCPnet.impl;

import java.util.logging.Logger;

import net.sourceforge.jFuzzyLogic.FIS;
import net.sourceforge.jFuzzyLogic.defuzzifier.DefuzzifierCenterOfGravity;
import net.sourceforge.jFuzzyLogic.membership.MembershipFunction;
import net.sourceforge.jFuzzyLogic.membership.MembershipFunctionPieceWiseLinear;
import net.sourceforge.jFuzzyLogic.rule.FuzzyRuleSet;
import net.sourceforge.jFuzzyLogic.rule.LinguisticTerm;
import net.sourceforge.jFuzzyLogic.rule.Variable;
import net.sourceforge.jFuzzyLogic.ruleAggregation.RuleAggregationMethodMax;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.impl.ENotificationImpl;

import com.thalesgroup.preferenceFramework.models.Arc;
import com.thalesgroup.preferenceFramework.models.Node;
import com.thalesgroup.preferenceFramework.models.NodeValue;
import com.thalesgroup.preferenceFramework.models.LCPnet.CLPT;
import com.thalesgroup.preferenceFramework.models.LCPnet.CNodeValue;
import com.thalesgroup.preferenceFramework.models.LCPnet.COutcome;
import com.thalesgroup.preferenceFramework.models.LCPnet.Coordinate;
import com.thalesgroup.preferenceFramework.models.LCPnet.LCPnet;
import com.thalesgroup.preferenceFramework.models.LCPnet.LCPnetPackage;
import com.thalesgroup.preferenceFramework.models.LCPnet.LNode;
import com.thalesgroup.preferenceFramework.models.LCPnet.LinguisticDomain;
import com.thalesgroup.preferenceFramework.models.LCPnet.exceptions.LNodeEvaluationException;
import com.thalesgroup.preferenceFramework.models.LCPnet.exceptions.LCPnetEvaluationException;
import com.thalesgroup.preferenceFramework.models.exceptions.PreferenceEvaluationException;
import com.thalesgroup.preferenceFramework.models.exceptions.PreferenceException;
import com.thalesgroup.preferenceFramework.models.LCPnet.LinguisticSubset;
import com.thalesgroup.preferenceFramework.models.impl.NodeImpl;

/**
 * <!-- begin-user-doc --> An implementation of the model object '
 * <em><b>LNode</b></em>'. <!-- end-user-doc -->
 * <p>
 * The following features are implemented:
 * <ul>
 *   <li>{@link com.thalesgroup.preferenceFramework.models.LCPnet.impl.LNodeImpl#getLinguisticTable <em>Linguistic Table</em>}</li>
 *   <li>{@link com.thalesgroup.preferenceFramework.models.LCPnet.impl.LNodeImpl#getValueDomain <em>Value Domain</em>}</li>
 * </ul>
 * </p>
 *
 * @generated
 */
public class LNodeImpl extends NodeImpl implements LNode {
	private static Logger logger = Logger.getLogger(LNodeImpl.class.getName());
	protected FIS fis = null;

	/**
	 * The cached value of the '{@link #getLinguisticTable() <em>Linguistic Table</em>}' containment reference.
	 * <!-- begin-user-doc
	 * --> <!-- end-user-doc -->
	 * @see #getLinguisticTable()
	 * @generated
	 * @ordered
	 */
	protected CLPT linguisticTable;

	/**
	 * The cached value of the '{@link #getValueDomain() <em>Value Domain</em>}' reference.
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @see #getValueDomain()
	 * @generated
	 * @ordered
	 */
	protected LinguisticDomain valueDomain;

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	protected LNodeImpl() {
		super();
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	protected EClass eStaticClass() {
		return LCPnetPackage.Literals.LNODE;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public CLPT getLinguisticTable() {
		return linguisticTable;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public NotificationChain basicSetLinguisticTable(CLPT newLinguisticTable,
			NotificationChain msgs) {
		CLPT oldLinguisticTable = linguisticTable;
		linguisticTable = newLinguisticTable;
		if (eNotificationRequired()) {
			ENotificationImpl notification = new ENotificationImpl(this, Notification.SET, LCPnetPackage.LNODE__LINGUISTIC_TABLE, oldLinguisticTable, newLinguisticTable);
			if (msgs == null) msgs = notification; else msgs.add(notification);
		}
		return msgs;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public void setLinguisticTable(CLPT newLinguisticTable) {
		if (newLinguisticTable != linguisticTable) {
			NotificationChain msgs = null;
			if (linguisticTable != null)
				msgs = ((InternalEObject)linguisticTable).eInverseRemove(this, LCPnetPackage.CLPT__NODE, CLPT.class, msgs);
			if (newLinguisticTable != null)
				msgs = ((InternalEObject)newLinguisticTable).eInverseAdd(this, LCPnetPackage.CLPT__NODE, CLPT.class, msgs);
			msgs = basicSetLinguisticTable(newLinguisticTable, msgs);
			if (msgs != null) msgs.dispatch();
		}
		else if (eNotificationRequired())
			eNotify(new ENotificationImpl(this, Notification.SET, LCPnetPackage.LNODE__LINGUISTIC_TABLE, newLinguisticTable, newLinguisticTable));
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public LinguisticDomain getValueDomain() {
		if (valueDomain != null && valueDomain.eIsProxy()) {
			InternalEObject oldValueDomain = (InternalEObject)valueDomain;
			valueDomain = (LinguisticDomain)eResolveProxy(oldValueDomain);
			if (valueDomain != oldValueDomain) {
				if (eNotificationRequired())
					eNotify(new ENotificationImpl(this, Notification.RESOLVE, LCPnetPackage.LNODE__VALUE_DOMAIN, oldValueDomain, valueDomain));
			}
		}
		return valueDomain;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public LinguisticDomain basicGetValueDomain() {
		return valueDomain;
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	public void setValueDomain(LinguisticDomain newValueDomain) {
		LinguisticDomain oldValueDomain = valueDomain;
		valueDomain = newValueDomain;
		if (eNotificationRequired())
			eNotify(new ENotificationImpl(this, Notification.SET, LCPnetPackage.LNODE__VALUE_DOMAIN, oldValueDomain, valueDomain));
	}

	public double getUtility(EList<CNodeValue> inputValues, CNodeValue nodeValue) throws LNodeEvaluationException {
		// 0 check values boundaries before preference evaluation
		for (CNodeValue inputValue : inputValues) {
			LinguisticDomain inputValueDomain = ((LNode) inputValue.getNode()).getValueDomain();
			Double variableValue = inputValue.getCrispValue();
			if(variableValue < inputValueDomain.getLowerBound() || variableValue > inputValueDomain.getUpperBound()) {
				throw new LNodeEvaluationException("input value \"" + inputValue.getName() + "\" boundaries error : value="+ variableValue);
			}
		}
		{
			LinguisticDomain valueDomain = this.getValueDomain();
			Double variableValue = nodeValue.getCrispValue();
			if(variableValue < valueDomain.getLowerBound() || variableValue > valueDomain.getUpperBound()) {
				throw new LNodeEvaluationException("input value \"" + nodeValue.getName() + "\" boundaries error : value="+ variableValue);
			}
		}

		// 1 - Set node input values in fuzzy rule set
		FIS fis = this.getFuzzyInferenceSystem();
		FuzzyRuleSet fuzzyRuleSet = fis.getFuzzyRuleSet();

		for (CNodeValue inputValue : inputValues) {
			String variableName = inputValue.getNode().getName();
			Double variableValue = inputValue.getCrispValue();
			fuzzyRuleSet.setVariable(variableName, variableValue);
		}

		// 2 - Set this node value in fuzzy rule set
		String variableName = nodeValue.getNode().getName();
		Double variableValue = nodeValue.getCrispValue();
		fuzzyRuleSet.setVariable(variableName, variableValue);

		// 3 - Reset rule set and run evaluation
		fuzzyRuleSet.evaluate();

		// 4 - Obtain utility value for this evaluation
		LCPnet thisLCPNet = (LCPnet) this.getPreferenceNetwork();
		String utilityName = thisLCPNet.getUtilityDomain().getName();
		Variable utilityVariable = fuzzyRuleSet.getVariable(utilityName);

		double utilityVariableValue = utilityVariable.getLatestDefuzzifiedValue();
		if(Double.isNaN(utilityVariableValue)) {
			throw new LNodeEvaluationException("No utility value for node " + this.getName() + "after evaluation");
		}
		return utilityVariableValue;
	}

	public double getUtility(COutcome outcome)  throws LNodeEvaluationException {
		CNodeValue nodeValue;
		try {
			nodeValue = (CNodeValue) outcome.getNodeValue(this);
		} catch (PreferenceException e) {
			throw new LNodeEvaluationException(e.getMessage());
		}
		EList<NodeValue> inputValues = outcome.getParentValues(nodeValue);
		
		EList<CNodeValue> cinputValues = new BasicEList<CNodeValue>();
		for (NodeValue value : inputValues) {
			cinputValues.add((CNodeValue) value);
		}
		
		return this.getUtility(cinputValues, nodeValue);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public NotificationChain eInverseAdd(InternalEObject otherEnd,
			int featureID, NotificationChain msgs) {
		switch (featureID) {
			case LCPnetPackage.LNODE__LINGUISTIC_TABLE:
				if (linguisticTable != null)
					msgs = ((InternalEObject)linguisticTable).eInverseRemove(this, EOPPOSITE_FEATURE_BASE - LCPnetPackage.LNODE__LINGUISTIC_TABLE, null, msgs);
				return basicSetLinguisticTable((CLPT)otherEnd, msgs);
		}
		return super.eInverseAdd(otherEnd, featureID, msgs);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public NotificationChain eInverseRemove(InternalEObject otherEnd,
			int featureID, NotificationChain msgs) {
		switch (featureID) {
			case LCPnetPackage.LNODE__LINGUISTIC_TABLE:
				return basicSetLinguisticTable(null, msgs);
		}
		return super.eInverseRemove(otherEnd, featureID, msgs);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public Object eGet(int featureID, boolean resolve, boolean coreType) {
		switch (featureID) {
			case LCPnetPackage.LNODE__LINGUISTIC_TABLE:
				return getLinguisticTable();
			case LCPnetPackage.LNODE__VALUE_DOMAIN:
				if (resolve) return getValueDomain();
				return basicGetValueDomain();
		}
		return super.eGet(featureID, resolve, coreType);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public void eSet(int featureID, Object newValue) {
		switch (featureID) {
			case LCPnetPackage.LNODE__LINGUISTIC_TABLE:
				setLinguisticTable((CLPT)newValue);
				return;
			case LCPnetPackage.LNODE__VALUE_DOMAIN:
				setValueDomain((LinguisticDomain)newValue);
				return;
		}
		super.eSet(featureID, newValue);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public void eUnset(int featureID) {
		switch (featureID) {
			case LCPnetPackage.LNODE__LINGUISTIC_TABLE:
				setLinguisticTable((CLPT)null);
				return;
			case LCPnetPackage.LNODE__VALUE_DOMAIN:
				setValueDomain((LinguisticDomain)null);
				return;
		}
		super.eUnset(featureID);
	}

	/**
	 * <!-- begin-user-doc --> <!-- end-user-doc -->
	 * @generated
	 */
	@Override
	public boolean eIsSet(int featureID) {
		switch (featureID) {
			case LCPnetPackage.LNODE__LINGUISTIC_TABLE:
				return linguisticTable != null;
			case LCPnetPackage.LNODE__VALUE_DOMAIN:
				return valueDomain != null;
		}
		return super.eIsSet(featureID);
	}

	public FIS getFuzzyInferenceSystem() throws LNodeEvaluationException {
		// Singleton pattern : the FIS doesn't need to be recalculated each time
		if (fis == null) {
			fis = new FIS();

			// only one ruleSet for a node
			FuzzyRuleSet ruleSet = new FuzzyRuleSet();

			// initialize rule set
			this.setInputVariables(ruleSet);
			this.setOutputVariable(ruleSet);
			this.getLinguisticTable().setRules(ruleSet);

			fis.addFuzzyRuleSet("node " + this.getName(), ruleSet);
		}
		return fis;
	}

	/**
	 * Initialize input variables in the rule set based on input nodes.
	 */
	private void setInputVariables(FuzzyRuleSet ruleSet) {
		// input nodes -> input variables of the fuzzy rule set
		EList<Node> nodesToBeAdded = this.inputNodes();
		// this node -> input variable too
		nodesToBeAdded.add(this);

		for (Node node : nodesToBeAdded) {
			LNode lnode = (LNode) node;
			String nodeName = lnode.getName();
			if (!ruleSet.varibleExists(nodeName)) {
				// 1 - create corresponding input variable declaration as a Real
				// (node name = variable name)
				Variable inputVariable = new Variable(nodeName);

				// 2 - create corresponding input variable fuzzy definition
				LinguisticDomain nodeDomain = lnode.getValueDomain();
				EList<LinguisticSubset> domainSubsets = nodeDomain.getSubsets();

				// convert each fuzzy subset to a membership function
				for (LinguisticSubset subset : domainSubsets) {

					EList<Coordinate> coordinates = subset.getFuzzySubset();
					double[] x = new double[coordinates.size()];
					double[] y = new double[coordinates.size()];
					for (int i = 0; i < coordinates.size(); i++) {
						x[i] = coordinates.get(i).getX();
						y[i] = coordinates.get(i).getY();
					}

					MembershipFunction membershipFunction = new MembershipFunctionPieceWiseLinear(
							x, y);
					LinguisticTerm linguisticTerm = new LinguisticTerm(subset
							.getName(), membershipFunction);

					inputVariable.add(linguisticTerm);
				}

				// 3 - add variable to rule set
				ruleSet.getVariables().put(nodeName, inputVariable);
			} else {
				logger.warning("Variable " + nodeName + " already exists !");
			}
		}
	}

	/**
	 * Initialize output variable in the rule set based on the declared utility
	 * domain.
	 */
	private void setOutputVariable(FuzzyRuleSet ruleSet) {
		LCPnet net = (LCPnet) this.getPreferenceNetwork();
		LinguisticDomain utilityDomain = net.getUtilityDomain();

		// add utility to the list of output variables
		String utilityName = utilityDomain.getName();
		if (!ruleSet.varibleExists(utilityName)) {
			// 1 - create corresponding output variable declaration as a Real
			Variable outputVariable = new Variable(utilityName);

			// 2 - create corresponding output variable fuzzy definition
			EList<LinguisticSubset> utilitySubsets = utilityDomain.getSubsets();

			// convert each fuzzy subset to a membership function
			for (LinguisticSubset subset : utilitySubsets) {

				EList<Coordinate> coordinates = subset.getFuzzySubset();
				double[] x = new double[coordinates.size()];
				double[] y = new double[coordinates.size()];
				for (int i = 0; i < coordinates.size(); i++) {
					x[i] = coordinates.get(i).getX();
					y[i] = coordinates.get(i).getY();
				}

				MembershipFunction membershipFunction = new MembershipFunctionPieceWiseLinear(
						x, y);
				LinguisticTerm linguisticTerm = new LinguisticTerm(subset
						.getName(), membershipFunction);

				outputVariable.add(linguisticTerm);
			}

			// 3 - configure defuzzification
			outputVariable
					.setRuleAggregationMethod(new RuleAggregationMethodMax());
			outputVariable.setDefuzzifier(new DefuzzifierCenterOfGravity(
					outputVariable));

			// variable's default value, used if no rule activates defuzzifier
			outputVariable.setDefaultValue(0.0);

			// 4 - add variable to rule set
			ruleSet.getVariables().put(utilityName, outputVariable);

		} else {
			logger.warning("Variable " + utilityName + " already exists !");
		}
	}

	/**
	 * Returns the nodes used as inputs in the preference table associated to
	 * this specific node. In the case of LCP-Nets with Arcs, I-Arcs and CI-Arcs
	 * this is NOT equivalent to the list of parent nodes.
	 * 
	 * It redefines the inputNodes() method of the NodeImpl class.
	 * 
	 * @return empty list if no input nodes
	 */
	public EList<Node> inputNodes() {
		EList<Arc> inArcs = this.getInArcs();
		BasicEList<Node> inputNodes = new BasicEList<Node>();
		for (Arc arc : inArcs) {
			if (!(arc instanceof IArcImpl) && !(arc instanceof CIArcImpl)) {
				Node parentNode = arc.getStartNode();
				inputNodes.add(parentNode);
			}
		}
		return inputNodes;
	}

} // LNodeImpl
